/**
 * CalculatorEngine provides an object capable of functioning as a basic four-function calculator.
 * A CalculatorEngine has two registers and uses infix notation.  As the client adds digits to
 * the CalculatorEngine, the number is stored in an active register. A value supplied to a
 * CalculatorEngine is stored in a second register once an operation is specified.  After a 
 * second value is supplied, a compute method may be called and the CalculatorEngine will
 * compute the results based on the given operation and current values in the two registers.
 */

package org.nbk.calculator;

public class CalculatorEngine {
	
	//Constants
	private static final String DEFAULT_VALUE = "";
	private static final char NO_OPERATION = '\n';
	private static final char ADDITION_OPERATOR = '+';
	private static final char SUBTRACTION_OPERATOR = '-';
	private static final char MULTIPLICATION_OPERATOR = '*';
	private static final char DIVISION_OPERATOR = '/';
	private static final char POWER_OPERATOR = '^';
	private static final int MAX_NUM_DIGITS = 11;
	
	//Instance variables
	//keep and value are registers
	private String keep, value;
	private char operation;
	
	/**
	 * Creates a new CalculatorEngine.
	 */
	public CalculatorEngine() {
		clear();
	}
	
	/**
	 * Restores a CalculatorEngine to its default state.
	 */
	public void clear() {
		keep = DEFAULT_VALUE;
		value = DEFAULT_VALUE;
		operation = NO_OPERATION;
	}
	
	/**
	 * Restores the active register to its default state.
	 * In other words, the value currently being entered into
	 * the CalculatorEngine is erased.
	 */
	public void clearEntry() {
		value = DEFAULT_VALUE;
	}
	
	/**
	 * Instructs the CalculatorEngine to anticipate addition.
	 */ 
	public void add() {
		binaryOperation(ADDITION_OPERATOR);
	}
	
	/**
	 * Instructs the CalculatorEngine to anticipate subtraction.
	 */ 
	public void subtract() {
		binaryOperation(SUBTRACTION_OPERATOR);
	}
	
	/**
	 * Instructs the CalculatorEngine to anticipate multiplication.
	 */ 
	public void multiply() {
		binaryOperation(MULTIPLICATION_OPERATOR);
	}
	
	/**
	 * Instructs the CalculatorEngine to anticipate division.
	 */ 
	public void divide() {
		binaryOperation(DIVISION_OPERATOR);
	}
	
	/**
	 * Instructs the CalculatorEngine to anticipate exponentiation.
	 */ 
	public void exponent() {
		binaryOperation(POWER_OPERATOR);
	}
	
	/*
	 * Stores the infix operator, stores the current value in keep,
	 * and resets the value register.
	 * -------------
	 * Parameters:
	 * char op: the operation to save for later
	 */
	private void binaryOperation(char op) {
		operation = op;
		keep = value;
		value = DEFAULT_VALUE;
	}
	
	/**
	 * Appends a new digit onto the end of the current value.
	 * @param n							digit to append
	 * @throws OutOfBoundsException		thrown if n is not between 0 and 9
	 */
	public void digit(int n) throws OutOfBoundsException {
		//Initial test is to prevent leading zeros
		if(value.equals(DEFAULT_VALUE) && n==0) return;
		//Ensure that n is between 0 and 9
		else if(n<=9 && n>=0) value += Integer.toString(n);
		else throw new OutOfBoundsException();
	}
	
	/**
	 * Appends a new digit onto the end of the current value.
	 * @param ch						digit to append
	 * @throws OutOfBoundsException		thrown if n is not between 0 and 9
	 */
	public void digit(char ch) throws OutOfBoundsException {
		//Initial test is to prevent leading zeros
		if(value.equals(DEFAULT_VALUE) && ch=='0') return;
		//Ensure that n is between 0 and 9
		else if(ch<='9' &&  ch>='0') value += ch;
		else throw new OutOfBoundsException();
	}
	
	/**
	 * Negates the current value.
	 */
	public void negate() {
		if(value.charAt(0) != '-') value = "-" + value;
		else value = value.substring(1);
	}
	
	/**
	 * Inserts a decimal point at the end of the current value. The digits 
	 * appended to the current value after this method is called will be
	 * to the right of the decimal point.
	 * @throws InvalidOperationException	thrown if method is called after
	 * 									    a decimal point has already been placed
	 */
	public void decimal() throws InvalidOperationException {
		if(!hasDecimal(value)) value += '.';
		else throw new InvalidOperationException("Decimal point already present");
	}
	
	/**
	 * Returns the contents of the active register
	 * @return		the contents of the active register
	 */
	public String getValue() {
		return value;
	}
	
	/**
	 * Returns true if the provided String contains a decimal point (period character).
	 * @param str		the String to be tested
	 * @return			true if decimal is found
	 */
	public static boolean hasDecimal(String str) {
		return str.contains(".");
	}
	
	public void backspace() {
		value = value.substring(0,value.length()-1);
	}
	
	/**
	 * Computes the supplied values using the specified operation.
	 * The result of this computation is stored in the "keep" register.
	 * This allows subsequent operations to be applied.
	 * @return		result of the computation
	 * @throws InvalidOperationException	thrown if no operation was
	 * 										specified prior to calling this method
	 */
	public String compute() throws InvalidOperationException {
		boolean decimalPresent = hasDecimal(keep) || hasDecimal(value);
		switch(operation) {
		case NO_OPERATION:
			throw new InvalidOperationException("No operation specified");
		case ADDITION_OPERATOR:
			value = decimalPresent ? decimalAddition(keep, value) : integerAddition(keep, value);
			break;
		case SUBTRACTION_OPERATOR:
			value = decimalPresent ? decimalSubtraction(keep, value) : integerSubtraction(keep, value);
			break;
		case MULTIPLICATION_OPERATOR:
			value = decimalPresent ? decimalMultiplication(keep, value) : integerMultiplication(keep, value);
			break;
		case DIVISION_OPERATOR:
			//Decimal division will always be applied.
			value = decimalDivision(keep, value);
			break;
		case POWER_OPERATOR:
			value = Double.toString(Math.pow(Double.valueOf(keep), Double.valueOf(value)));
			break;
		default:
			break;
		}
		return value;
	}
	
	/*
	 * Converts Strings a and b to doubles using the Double wrapper class,
	 * adds the two, and returns the result as a String
	 * ----------------
	 * Parameters:
	 * String a,b: Strings of the numbers to add (order doesn't matter)
	 */
	private static String decimalAddition(String a, String b) {
		double sum = Double.valueOf(a) + Double.valueOf(b);
		return Double.toString(sum);
	}
	
	/*
	 * Converts Strings a and b to longs using the Long wrapper class,
	 * adds the two, and returns the result as a String
	 * ----------------
	 * Parameters:
	 * String a,b: Strings of the numbers to add (order doesn't matter)
	 */
	private static String integerAddition(String a, String b) {
		long sum = Long.valueOf(a) + Long.valueOf(b);
		return Long.toString(sum);
	}
	
	/*
	 * Converts Strings minuend and subtrahend to doubles using 
	 * the Double wrapper class.  Computes minuend - subtrahend
	 * and returns the difference as a String.
	 * --------------
	 * Parameters:
	 * String minuend: the number to the left of the subtraction sign
	 * String subtrahend: the number to the right of the subtraction sign
	 */
	private static String decimalSubtraction(String minuend, String subtrahend) {
		double difference = Double.valueOf(minuend) - Double.valueOf(subtrahend);
		return Double.toString(difference);
	}
	
	/*
	 * Converts Strings minuend and subtrahend to ints using 
	 * the Long wrapper class.  Computes minuend - subtrahend
	 * and returns the difference as a String.
	 * --------------
	 * Parameters:	 
	 * String minuend: the number to the left of the subtraction sign
	 * String subtrahend: the number to the right of the subtraction sign
	 */
	private static String integerSubtraction(String minuend, String subtrahend) {
		long difference = Long.valueOf(minuend) - Long.valueOf(subtrahend);
		return Long.toString(difference);
	}
	
	/*
	 * Converts the provided strings to doubles using the Double wrapper
	 * class.  Returns the product of the two.
	 * -------------
	 * Parameters:
	 * String a,b: the two numbers to multiply
	 */
	private static String decimalMultiplication(String a, String b) {
		double product = Double.valueOf(a) * Double.valueOf(b);
		return Double.toString(product);
	}
	
	/*
	 * Converts the provided strings to longs using the Long wrapper
	 * class.  Returns the product of the two.
	 * -------------
	 * Parameters:
	 * String a,b: the two numbers to multiply
	 */
	private static String integerMultiplication(String a, String b) {
		long product = Long.valueOf(a) * Long.valueOf(b);
		return Long.toString(product);
	}
	
	/*
	 * Converts the provided strings to doubles and returns the quotient of
	 * dividend/divisor as a String.  Be particularly careful of the order
	 * of the arguments.
	 * -----------------
	 * Parameters:
	 * String dividend: number to the left of the '/' operator
	 * String divisor: number to the right of the '/' operator
	 */
	private static String decimalDivision(String dividend, String divisor) {
		double quotient = Double.valueOf(dividend) / Double.valueOf(divisor);
		return Double.toString(quotient);
	}
	
}
